using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;

////////////////////////////////////////////////////////////////////////////////////////////////////
// namespace: Dimok.Images
//
// summary:	.
////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Dimok.Images {

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>	Values that represent PageTypes. </summary>
	///
	/// <remarks>	Dimon, 12/21/2010. </remarks>
	////////////////////////////////////////////////////////////////////////////////////////////////////

	public enum PageTypes{UNKNOWN=-2,DEFAULT=-1,TIFF=0,WORD,PDF,XML,HTML};

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>	Enums. </summary>
	///
	/// <remarks>	Dimon, 12/21/2010. </remarks>
	////////////////////////////////////////////////////////////////////////////////////////////////////

	public class Enums {
		#region Page Type ====================

		#region Extentions
		private static readonly byte[] WORD_HEADER = {208,207,17,224,161,177,26};

		public const string FILE_EXT_TIFF = "tif";
		public const string FILE_EXT_WORD = "doc";
		public const string FILE_EXT_PDF	= "pdf";
		public const string FILE_EXT_XML	= "xml";
		public const string FILE_EXT_HTML	= "html";
		public readonly static string[] FILE_EXTENTIONS = new string[]{FILE_EXT_TIFF,FILE_EXT_WORD,FILE_EXT_PDF,FILE_EXT_XML,FILE_EXT_HTML};

		public static string GetExtention(string contentType){return GetExtention(GetPageType(contentType));}
		public static string GetExtention(byte[] image){return GetExtention(GetPageType(image));}
		public static string GetExtention(PageTypes pageType){
			if( pageType < 0 )throw new Exception("Unable to get extention of "+pageType+" page type.");
			return FILE_EXTENTIONS[(int)pageType];
		}
		#endregion ==========================================================

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets a content type. </summary>
		///
		/// <remarks>	Dimon, 12/21/2010. </remarks>
		///
		/// <param name="Filename">	Filename of the file. </param>
		///
		/// <returns>	The content type. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public string GetContentType(string Filename) {
			string mime = "application/octetstream";
			string ext = System.IO.Path.GetExtension(Filename).ToLower();
			Microsoft.Win32.RegistryKey rk = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(ext);
			if (rk != null && rk.GetValue("Content Type") != null)
				mime = rk.GetValue("Content Type").ToString();
			return mime;
		} 

		public const string CONTENT_TYPES_XML = "text/xml";
		public const string CONTENT_TYPES_HTML =  "text/html";
		public const string CONTENT_TYPES_TIFF = "image/tiff";
		public const string CONTENT_TYPES_WORD = "application/msword";
		public const string CONTENT_TYPES_PDF =  "application/pdf";
		public readonly static string[] CONTENT_TYPES = new string[]{CONTENT_TYPES_TIFF,CONTENT_TYPES_WORD,CONTENT_TYPES_PDF,CONTENT_TYPES_XML,CONTENT_TYPES_HTML};

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets a content type. </summary>
		///
		/// <remarks>	Dimon, 12/21/2010. </remarks>
		///
		/// <param name="image">	The image. </param>
		///
		/// <returns>	The content type. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static string GetContentType(byte[] image){
			try {
				return MimeTypeFrom(image,"text/plain");
			} catch {	}
			return GetContentType(GetPageType(image));
		}
		public static string GetContentType(PageTypes pageType){
			if( pageType < 0 )throw new Exception("Unable to get content-type of "+pageType+" page type.");
			return CONTENT_TYPES[(int)pageType];
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Searches for the first mime from data. </summary>
		///
		/// <remarks>	Dimon, 12/21/2010. </remarks>
		///
		/// <param name="pBC">							The bc. </param>
		/// <param name="pwzUrl">						URL of the pwz. </param>
		/// <param name="">									The. </param>
		/// <param name="cbSize">						Size of the cb. </param>
		/// <param name="pwzMimeProposed">	The pwz mime proposed. </param>
		/// <param name="dwMimeFlags">			The dw mime flags. </param>
		/// <param name="ppwzMimeOut">			[out] The ppwz mime out. </param>
		/// <param name="dwReserved">				The dw reserved. </param>
		///
		/// <returns>	The found mime from data. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		[DllImport("urlmon.dll",CharSet = CharSet.Unicode,ExactSpelling = true,SetLastError = false)]
		static extern int FindMimeFromData(IntPtr pBC,
					[MarshalAs(UnmanagedType.LPWStr)] string pwzUrl,
				 [MarshalAs(UnmanagedType.LPArray,ArraySubType = UnmanagedType.I1,SizeParamIndex = 3)] 
        byte[] pBuffer,
					int cbSize,
						 [MarshalAs(UnmanagedType.LPWStr)]  string pwzMimeProposed,
					int dwMimeFlags,
					out IntPtr ppwzMimeOut,
					int dwReserved);

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Mime type from. </summary>
		///
		/// <remarks>	Dimon, 12/21/2010. </remarks>
		///
		/// <exception cref="ArgumentNullException">	Thrown when one or more required arguments are null. </exception>
		///
		/// <param name="dataBytes">		The data in bytes. </param>
		/// <param name="mimeProposed">	The mime proposed. </param>
		///
		/// <returns>	. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static string MimeTypeFrom(byte[] dataBytes,string mimeProposed) {
			if(dataBytes == null)
				throw new ArgumentNullException("dataBytes");
			string mimeRet = String.Empty;
			IntPtr suggestPtr = IntPtr.Zero,filePtr = IntPtr.Zero,outPtr = IntPtr.Zero;
			if(mimeProposed != null && mimeProposed.Length > 0) {
				//suggestPtr = Marshal.StringToCoTaskMemUni(mimeProposed); // for your experiments ;-)
				mimeRet = mimeProposed;
			}
			int ret = FindMimeFromData(IntPtr.Zero,null,dataBytes,dataBytes.Length,mimeProposed,0,out outPtr,0);
			if(ret == 0 && outPtr != IntPtr.Zero) {
				//todo: this leaks memory outPtr must be freed
				try {
					return Marshal.PtrToStringUni(outPtr);
				} finally {
					Marshal.FreeCoTaskMem(outPtr);
				}
			}
			return mimeRet;
		}

		/// <summary>
		/// Ensures that file exists and retrieves the content type 
		/// </summary>
		/// <param name="file"></param>
		/// <returns>Returns for instance "images/jpeg" </returns>
		public static string getMimeFromFile(string file) {
			IntPtr mimeout;
			if (!System.IO.File.Exists(file))
				throw new FileNotFoundException(file + " not found");

			int MaxContent = (int)new FileInfo(file).Length;
			if (MaxContent > 4096) MaxContent = 4096;
			FileStream fs = File.OpenRead(file);


			byte[] buf = new byte[MaxContent];
			fs.Read(buf, 0, MaxContent);
			fs.Close();
			int result = FindMimeFromData(IntPtr.Zero, file, buf, MaxContent, null, 0, out mimeout, 0);

			if (result != 0)
				throw Marshal.GetExceptionForHR(result);
			string mime = Marshal.PtrToStringUni(mimeout);
			Marshal.FreeCoTaskMem(mimeout);
			return mime;
		}
		//rename crystal.jpg to .gif to test functionality!
		//string getImg = Environment.GetEnvironmentVariable("windir") + "\\Web\\WallPaper\\Crystal.gif";
		//string mime = getMimeFromFile(getImg);

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets a page type. </summary>
		///
		/// <remarks>	Dimon, 12/21/2010. </remarks>
		///
		/// <param name="contentType">	Type of the content. </param>
		///
		/// <returns>	The page type. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static PageTypes GetPageType(string contentType) {
			for( int i = 0; i < CONTENT_TYPES.Length; i++ )
				if( contentType.ToLower() == CONTENT_TYPES[i].ToLower() )
					return (PageTypes)i;
			/*
			if( !Path.HasExtension(contentType) ){
				PageTypes pageType = PageTypes.UNKNOWN;
				string[] exts = FILE_EXTENTIONS;
				foreach(string ext in exts ){
					try{
						pageType = GetPageType(contentType+"."+ext);
					}catch(PageTypeException){}
					if( pageType != PageTypes.UNKNOWN )return pageType;
				}
			}
			*/
			if( File.Exists(contentType) ){
				FileStream fs = File.OpenRead(contentType);
				for( int i = 0; fs.Length == 0 && i < FILE_EXTENTIONS.Length; i++ )
					if( Path.GetExtension(contentType).ToLower() == "."+FILE_EXTENTIONS[i].ToLower() )
						return (PageTypes)i;
				byte[] image = new byte[Math.Min(fs.Length,10)];
				fs.Read(image,0,image.Length);
				try{
					return GetPageType(image);
				}finally{
					fs.Close();
				}
			}else
				for( int i = 0; i < FILE_EXTENTIONS.Length; i++ )
					if( Path.GetExtension(contentType).ToLower() == "."+FILE_EXTENTIONS[i].ToLower() )
						return (PageTypes)i;
			throw new EmptyFileException(contentType,false);
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets a page type. </summary>
		///
		/// <remarks>	Dimon, 12/21/2010. </remarks>
		///
		/// <exception cref="EmptyFileException">	Thrown when emptyfile. </exception>
		/// <exception cref="PageTypeException">	Thrown when pagetype. </exception>
		///
		/// <param name="image">	The image. </param>
		///
		/// <returns>	The page type. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static PageTypes GetPageType(byte[] image){
			if( image == null || image.Length == 0 )throw new EmptyFileException();
			string header = Encoding.ASCII.GetString(image,0,Math.Min(image.Length,10)).ToUpper();
			string headerUC = Encoding.Unicode.GetString(image,0,Math.Min(image.Length,10)).ToUpper();
			if( header.IndexOf("II*") == 0 )return PageTypes.TIFF;
			if( header.IndexOf("%PDF-") == 0 )return PageTypes.PDF;
			if( Regex.IsMatch(header,"\\s*<HTML",RegexOptions.IgnoreCase|RegexOptions.Compiled) )
				return PageTypes.HTML;
			if( Regex.IsMatch(header,"\\s*<[?]?XML",RegexOptions.IgnoreCase|RegexOptions.Compiled) )
				return PageTypes.XML;
			//string wordTemplate = "(?:"+Encoding.ASCII.GetString(WORD_HEADER)+@"|{\\rtf)";
			//if( Regex.IsMatch(header,wordTemplate,RegexOptions.IgnoreCase))return PageTypes.WORD;
			throw new PageTypeException("Unknown image format: ["+header+"]");
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets a page type by header. </summary>
		///
		/// <remarks>	Dimon, 12/21/2010. </remarks>
		///
		/// <param name="header">	The header. </param>
		///
		/// <returns>	The page type by header. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static PageTypes GetPageTypeByHeader(string header){
			if( header.IndexOf("II*") == 0 )return PageTypes.TIFF;
			if( header.IndexOf("%PDF-") == 0 )return PageTypes.PDF;
			if( Regex.IsMatch(header,"\\s*<HTML",RegexOptions.IgnoreCase|RegexOptions.Compiled) )
				return PageTypes.HTML;
			if( Regex.IsMatch(header,"\\s*<[?]?XML",RegexOptions.IgnoreCase|RegexOptions.Compiled) )
				return PageTypes.XML;
			string wordTemplate = "(?:"+Encoding.ASCII.GetString(WORD_HEADER)+@"|{\\rtf)";
			if( Regex.IsMatch(header,wordTemplate,RegexOptions.IgnoreCase))return PageTypes.WORD;
			return PageTypes.UNKNOWN;
		}
		#endregion Page Type ====================
	}
}
